Skip to content

[WIP] add SWE-bench Lite accuracy eval / 添加 SWE-bench Lite 准确率评估#1947

Open
adibarra wants to merge 47 commits into
mainfrom
feat/swebench-lite-eval
Open

[WIP] add SWE-bench Lite accuracy eval / 添加 SWE-bench Lite 准确率评估#1947
adibarra wants to merge 47 commits into
mainfrom
feat/swebench-lite-eval

Conversation

@adibarra

@adibarra adibarra commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds SWE-bench Lite as the accuracy evaluation for single-node agentic-coding recipes. Fixed-sequence-length recipes continue to use lm-eval tasks such as GSM8K; run_eval selects the framework from the scenario and still accepts an explicit override.

Execution path

  1. run_eval dispatches agentic recipes to run_swebench_eval.
  2. mini-swe-agent 2.4.5 runs one coding agent per SWE-bench Lite instance against the model's local OpenAI-compatible endpoint.
  3. swe-rex 1.4.0 creates the per-instance execution sandboxes on Modal. Agent concurrency defaults to the recipe concurrency and can be overridden with SWEBENCH_AGENT_WORKERS.
  4. The generated prediction set is scored by the official SWE-bench 4.1.0 harness. CI uses Modal scoring sandboxes; local Docker remains available.
  5. swebench_score.py publishes resolved rate in the existing lm-eval result shape, so artifact collection and validate_scores.py use the same downstream path as the other accuracy evaluations.

The CI default is a 50-instance slice. EVAL_LIMIT=full (or 0) evaluates the complete SWE-bench Lite split. The score gate is exact_match,resolved >= 0.50; the denominator is the submitted instance count for sliced runs and the full set for full runs.

Recipe and workflow integration

  • Enables eval-only gating for all single-node agentic-coding recipes.
  • Preserves benchmark-only runs: the agentic accuracy evaluation is entered only by the existing eval-only/all-evals controls.
  • Threads Modal credentials through the benchmark workflow and bootstraps ~/.modal.toml when CI supplies token environment variables.
  • Uploads predictions, trajectories, official harness reports, and the normalized result JSON even when generation or scoring fails.
  • Adds watchdog and timeout controls for agent generation, per-instance scoring, and whole-run scoring so stalled sandboxes do not hold the GPU job indefinitely.

Runtime compatibility patches

The external packages are pinned and patched at runtime because the required fixes are not available in the pinned releases:

  • mini-swe-agent: terminate the environment on every exit and submit a valid working-tree diff when the agent exhausts its step budget without a submission.
  • swe-rex: correct Modal sandbox termination and clean up startup failures; use an app name scoped to this evaluation.
  • SWE-bench Modal scorer: reserve the configured CPU count and terminate each scoring sandbox when its instance completes.
  • lm-eval: accept reasoning_content when content is empty and avoid adding typed text wrappers unsupported by the TRT endpoint.

The patch implementations live in utils/evals/patches/; benchmark_lib.sh only installs pinned dependencies and invokes those scripts. Every source rewrite is anchor-checked, idempotent, and atomic: an upstream source mismatch fails without writing a partial patch.

Validation

Successful end-to-end workflow runs on the PR branch:

Run Scope Result
29061184942 Production-path defaults, 50 instances 32/50 resolved (64%)
29109355974 Post-review merge-readiness validation, 50 instances 36/50 resolved (72%)
29123068609 Full workflow and sandbox lifecycle cost validation, 50 instances 38/50 resolved (76%)
29163378164 App-scoped sweep and concurrency-matched workers, 50 instances 38/50 resolved (76%)

All four workflow runs completed successfully, including generation, official scoring, artifact collection, score validation, and the hardware success-rate job.

@adibarra adibarra requested a review from a team June 26, 2026 23:56
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for the contribution! For vLLM & SGLang, please ensure that your recipes is similar to the official vLLM recipes and/or the SGLang cookbook

If it is not, please create a PR first before we can merge your single node PR into the master branch. Let's ensure that the documentation is first class such that the entire ML community can benefit from your hard work! Thank you

PR authors are responsible for ensuring that after merging, all GitHub Action jobs fully pass. A lot of the time, failures are just flakes and simply re-running the failed jobs will fix it. If re-running failed jobs is attempted, PR authors are responsible for ensuring it passes. See GitHub's docs on re-running failed jobs: https://docs.github.com/en/actions/how-tos/manage-workflow-runs/re-run-workflows-and-jobs#re-running-failed-jobs-in-a-workflow

As a rule of thumb, generally, PR authors should request a review & get a PR approval from the respective companies' CODEOWNERS before requesting a review from core maintainers.

If additional help is needed, PR authors can reach out to core maintainers over Slack.


感谢你的贡献!对于 vLLM 与 SGLang,请确保你的 recipe 与官方 vLLM recipes 和/或 SGLang cookbook 保持一致

如果不一致,请先创建一个 PR,之后我们才能将你的单节点 PR 合并到 master 分支。让我们确保文档保持一流水准,使整个 ML 社区都能从你的辛勤工作中受益!谢谢

PR 作者有责任确保合并后所有 GitHub Action 任务完全通过。 很多时候失败只是偶发抖动(flake),重新运行失败的任务即可解决。如果选择重新运行失败的任务,PR 作者有责任确保其最终通过。参见 GitHub 关于重新运行失败任务的文档:https://docs.github.com/en/actions/how-tos/manage-workflow-runs/re-run-workflows-and-jobs#re-running-failed-jobs-in-a-workflow

一般而言,PR 作者应先向相应公司的 CODEOWNERS 请求审阅并获得 PR 批准,然后再请求核心维护者审阅。

如需更多帮助,PR 作者可通过 Slack 联系核心维护者。

Comment thread utils/evals/swebench_score.py Fixed
Comment thread utils/evals/swebench_score.py
Comment thread benchmarks/benchmark_lib.sh
adibarra added 4 commits July 2, 2026 12:13
…arness scoring, Modal-capable)

Add a SWE-bench Lite accuracy eval that generates patches via the lm-eval
harness and scores them with the official swebench evaluation harness.

- utils/evals/swebench_lite.yaml: lm-eval task config for SWE-bench Lite
  generation (prompt/doc-to-text, generation kwargs, dataset wiring).
- utils/evals/swebench_score.py: post-processing + scoring. Extracts model
  patches from lm-eval output, feeds them to the swebench harness, and emits
  a "resolved" rate. Supports running the harness locally or on Modal via
  SWEBENCH_USE_MODAL (Modal pass-through so scoring can run off-box).
- utils/collect_eval_results.py: extract_lm_metrics learns a "resolved"
  filter branch so the swebench resolved metric is collected alongside the
  existing lm-eval metrics.
- utils/evals/thresholds.json: add the swebench_lite threshold entry.
- utils/evals/EVALS.md: document the SWE-bench Lite eval and how scoring works.
- benchmarks/benchmark_lib.sh: add run_swebench_eval, _install_swebench_deps,
  maybe_run_eval, and Modal pass-through. run_eval now picks a per-scenario
  default framework (agentic-coding -> swebench, fixed-seq-len -> lm-eval);
  an explicit EVAL_FRAMEWORK env var or --framework arg overrides the default.
  EVAL_TASKS_DIR selects the task yaml.
- utils/evals/test_swebench_eval.py, utils/evals/test_run_eval_dispatch.py:
  tests for the scorer and the scenario/framework dispatch precedence.
…gentic configs)

Wire the SWE-bench Lite eval into the sweep matrix so it runs on agentic
coding configs, and route it through e2e-tests.

- utils/matrix_logic/generate_sweep_configs.py: add mark_eval_entries and
  mark_all_eval_entries. For agentic configs these mark exactly one eval
  entry per (model, runner, framework, precision) group at the highest
  concurrency, single-node only, so each unique agentic config gets one
  swebench eval run rather than one per concurrency point.
- utils/matrix_logic/test_generate_sweep_configs.py: add
  test_marks_agentic_entry_for_swebench and update TestMarkAllEvalEntries
  to cover the agentic marking behavior.
- .github/workflows/e2e-tests.yml: add the agentic-eval-config bucket, a
  test-sweep-agentic-evals job, and make collect-evals depend on it. The
  AGENTIC_EVAL filter (agentic + no prefill + run-eval) selects the eval
  entries; the throughput AGENTIC filter (agentic + not run-eval) excludes
  them so throughput and eval runs don't collide.
- benchmarks/single_node/agentic/kimik2.5_fp4_b300.sh: add the eval hook so
  the recipe triggers the agentic swebench eval.
…1.0) + bootstrap Modal creds from env

swebench 4.1.0 exposes --max_workers in both Docker and Modal modes; --parallelism
does not exist. Fix run_harness() to emit --max_workers in the Modal branch.

Add _ensure_modal_credentials() to benchmark_lib.sh: swebench's credential
check only looks for ~/.modal.toml, but CI supplies MODAL_TOKEN_ID/
MODAL_TOKEN_SECRET env vars (GitHub secret). The helper bootstraps the file
from the env vars when the file is absent, so the harness check passes. Called
in run_swebench_eval() right after _install_swebench_deps, scoring path only.

Update the Modal test name and assertions, the run_swebench_eval docstring,
and the EVALS.md knobs bullet to document the credential bootstrapping.
Apply the EVAL_ONLY=true if/else gating pattern (already present in
kimik2.5_fp4_b300.sh) to the remaining 24 single-node agentic recipes in
benchmarks/single_node/agentic/. In eval-only mode each recipe skips the
multi-turn agentic replay and calls maybe_run_eval "$PORT" against the live
server; run_eval auto-selects swebench for the agentic-coding scenario.
The deprecated/ subdirectory was not touched.
@adibarra adibarra force-pushed the feat/swebench-lite-eval branch from ce358e4 to e34035d Compare July 2, 2026 18:44
…job env

GitHub secrets MODAL_TOKEN_ID/MODAL_TOKEN_SECRET are now available; bootstrap
into ~/.modal.toml happens in benchmark_lib.sh:_ensure_modal_credentials.
SWEBENCH_USE_MODAL is only read by swebench-path functions, so it is inert for
lm-eval/gsm8k jobs.
@functionstackx functionstackx changed the title [WIP] add SWE-bench Lite accuracy eval [WIP] add SWE-bench Lite accuracy eval / 添加 SWE-bench Lite 准确率评估 Jul 4, 2026
adibarra added 18 commits July 6, 2026 17:36
# Conflicts:
#	benchmarks/single_node/agentic/dsr1_fp4_b200.sh
#	benchmarks/single_node/agentic/dsr1_fp4_mi355x.sh
#	benchmarks/single_node/agentic/glm5.1_fp4_mi355x.sh
#	benchmarks/single_node/agentic/glm5_fp8_b200.sh
#	benchmarks/single_node/agentic/gptoss_fp4_b200.sh
#	benchmarks/single_node/agentic/gptoss_fp4_h100.sh
#	benchmarks/single_node/agentic/gptoss_fp4_h200.sh
#	benchmarks/single_node/agentic/gptoss_fp4_mi300x.sh
#	benchmarks/single_node/agentic/gptoss_fp4_mi325x.sh
- Re-sync test-sweep-agentic-evals inputs with main's test-sweep-agentic:
  offloading -> kv-offloading + kv-offload-backend + total-cpu-dram-gb.
- Add EVAL_ONLY/maybe_run_eval tail gating to the agentic recipes AgentX
  v1.0 added (dsv4_fp4_b200_sglang, dsv4_fp4_b300_sglang, minimaxm3_fp8_h100/
  h200/mi300x/mi325x) so eval-only runs skip the replay like the others.
- test_run_eval_dispatch: set KV_OFFLOADING=none so the new source-time
  agentic guard in benchmark_lib.sh is satisfied (dispatch logic unaffected).
…dling

Add EVAL_LIMIT env var to run_lm_eval() so --limit N is appended to the
lm_eval invocation when set, enabling small smoke runs (e.g. 10 instances)
without touching the full ~300-instance swebench suite. Wire the knob through
benchmark-tmpl.yml (new eval-limit input + EVAL_LIMIT env) and e2e-tests.yml
(both workflow_dispatch and workflow_call inputs; passed through to
test-sweep-evals and test-sweep-agentic-evals with: blocks). Document the
variable in utils/evals/EVALS.md.

Harden _ensure_modal_credentials against b300 slurm/pyxis containers where
--export=ALL propagates the HOST's HOME into the container; if HOME is unset,
mkdir -p fails, or the directory isn't writable, remap HOME to
/tmp/inferencex-modal-home before writing ~/.modal.toml. Remap is scoped to
the write path (SWEBENCH_USE_MODAL=true, file absent, tokens present).

Tests: functional shim tests for --limit presence/absence; HOME-remap tests
covering writable home (no remap), read-only parent (remap + 600 perms), and
non-writable existing dir (remap); and a no-op test when SWEBENCH_USE_MODAL=false.
…mpty SWEBENCH_NAMESPACE arg

- Add `include_agentic: bool = False` to `mark_eval_entries`; wrap the
  `ag_sn_groups` agentic-marking block in `if include_agentic:` so that
  default sweeps no longer set `run-eval: true` on any agentic entry.
  The e2e-tests.yml AGENTIC filter (`not x.get('run-eval', False)`) then
  routes all agentic entries to the throughput job, restoring main parity.
- Pass `include_agentic=args.evals_only or args.all_evals` in `main()` so
  --evals-only and --all-evals continue to mark and select agentic entries.
- Replace `${SWEBENCH_NAMESPACE+--namespace "$SWEBENCH_NAMESPACE"}` with an
  `ns_args` array in `run_swebench_eval`; when `SWEBENCH_NAMESPACE=""` the
  old form word-split to a bare `--namespace` (argparse error); the array
  form safely expands `--namespace ""` or nothing when unset.
- Tests: `test_marks_agentic_entry_for_swebench` updated to pass
  `include_agentic=True`; new `test_default_mode_does_not_mark_agentic`
  asserts zero agentic entries marked in default mode; new ns_args unit
  tests cover unset/empty/value cases plus a static assertion that the old
  pattern is gone from benchmark_lib.sh.
… KeyErrors on unregistered task-name paths)

The pinned lm-eval (0.4.9.2, ref b315ef3) crashes with
KeyError: '<task_name>' in pretty_print_task (tasks/__init__.py:681) when
--tasks is given a file path to an external YAML whose task: name is not in
lm-eval's bundled registry.  gsm8k/gpqa_diamond are immune because those
names exist in the bundled registry; swebench_lite is not.

Fix: in run_lm_eval(), add optional EVAL_INCLUDE_PATH support — when set,
injects --include_path "$EVAL_INCLUDE_PATH" just before --tasks; inert when
unset (gsm8k/gpqa production invocations are byte-identical).

In run_swebench_eval(), switch the generation call from
  EVAL_TASKS_DIR="$yaml_path"     (path form → KeyError)
to
  EVAL_TASKS_DIR="$task_name"     (name form)
  EVAL_INCLUDE_PATH="$(dirname "$yaml_path")"   (registers the dir)
with save/restore of both vars so EVAL_INCLUDE_PATH does not leak to
subsequent lm-eval invocations.  The dataset_path-from-YAML derivation
(awk over yaml_path) is unchanged — generation and scoring remain in lockstep.

Tests: two shim-based dynamic tests (EVAL_INCLUDE_PATH set/unset → flag
present/absent in argv; --tasks carries name vs. yaml path) and one static
assertion that run_swebench_eval source contains EVAL_INCLUDE_PATH wiring.
…iling newline fail validation)

Live probe proved it: MODAL_TOKEN_SECRET secret has a trailing whitespace char;
raw auth fails ('Token validation failed'), whitespace-stripped auth succeeds.
Strip whitespace/quotes and re-export in _ensure_modal_credentials so both the
modal client (env) and the bootstrapped ~/.modal.toml are clean.
…lure

- run_swebench_eval: wrap scoring in timeout ${SWEBENCH_SCORE_TIMEOUT:-7200}s.
  The overnight 300-instance run stalled ~7h in Modal image builds and held the
  b300 allocation until the slurm wall; a stalled backend now fails fast.
- maybe_run_eval: always stage eval artifacts (append_lm_eval_summary) even when
  the eval fails, then propagate the rc — samples/predictions survive for
  diagnosis instead of dying in the job sandbox.
…ation input)

Agent harnesses (SWE-agent / mini-swe-agent) emit standard predictions.jsonl
directly; this bypasses lm-eval samples parsing and feeds the existing Modal
scoring + results pipeline unchanged. Groundwork for agentic swebench.
…sandboxes

SWEBENCH_GEN_MODE=agentic runs a real agent loop per instance instead of the
single-shot prompt: mini-swe-agent (2.4.5) drives the local OpenAI-compatible
endpoint; each instance's shell executes in a Modal sandbox (swe-rex[modal],
official swebench per-instance images -- no docker needed on the GPU node).
preds.json feeds the existing Modal scoring via --predictions-file (which now
also accepts the dict-keyed preds.json format directly).

- benchmark_lib.sh: _run_swebench_agentic_generation (config overlay, slice via
  EVAL_LIMIT, workers/step/timeout knobs), _install_swebench_agent_deps
  (mini-swe-agent==2.4.5 + swe-rex[modal]==1.4.0), gen-mode branch in
  run_swebench_eval feeding scoring via score_input array.
- swebench_score.py: --predictions-file accepts dict preds.json or JSONL.
- workflows: swebench-gen-mode input threaded e2e-tests -> benchmark-tmpl env.
- tests: shim-driven agentic-generation test + predictions-file format tests.

Single-shot remains the default; agentic is the real SWE-bench setting.
Fresh installs print a multi-line version banner on import; take only the last
stdout line and validate it is a file. Shim test now emulates the banner.
mini's default startup_timeout=60s is consumed by the cold GB-scale swebench
image pull alone ('Runtime did not start within 0s'). Default 900s via
SWEBENCH_AGENT_STARTUP_TIMEOUT; command timeout 300s (mini default 60s is too
tight for running repo test suites) via SWEBENCH_AGENT_CMD_TIMEOUT.
Trajectories are the primary forensic artifact for agent tuning; they
previously died with the job's temp dir. Copy *.traj* flat into the eval
output (append_lm_eval_summary flattens *.json* into the workspace root),
upload via new globs, and clean up post-upload.
Findings from 10-trajectory deep-dive (first-10 Lite, DSv4):
- 3/5 unresolved agents submitted without ever running the failing test
- 1 agent had the CORRECT fix on disk at step 31, burned 44 steps fighting an
  unfixable sandbox C-extension build, and hit the step cap without submitting
- CoT leaks into visible content (deepseek_v4 reasoning parser init failure,
  recipe-side follow-up) -- 'execute over prose' guidance mitigates

Replace the static config heredoc with a runtime merger that appends targeted
guidance to mini's instance_template: verify-before-submit, build-failure
escape hatch, submission discipline, step-budget framing. Single merged config
replaces the dual -c chain.
Every agent sandbox was billing a full hour for ~7-minute instances
(observed: batches dying at 59m59s on the Modal dashboard). Three leaks:

- mini-swe-agent 2.4.5 process_instance() never calls env.stop(), even on
  success, so every sandbox lives until runtime_timeout (3600s default).
- swe-rex 1.4.0 ModalDeployment.stop() has its poll check inverted: it
  terminates only sandboxes that already exited and skips running ones.
- ModalDeployment.start() leaks the sandbox when the runtime never comes
  alive (the startup-timeout failure mode).

Fix: _patch_swebench_agent_cleanup() patches the installed files at dep
install (idempotent, anchor-checked against the pinned versions) so
sandboxes terminate the moment their instance finishes; a post-generation
workspace sweep reaps anything that slips through (crashed workers, outer
timeout kills; SWEBENCH_SANDBOX_SWEEP=0 disables for tests); and the
merged config now sets runtime_timeout explicitly
(SWEBENCH_AGENT_RUNTIME_TIMEOUT, default 3600) as a pure backstop.

No agent-visible behavior change: cleanup happens after instance
completion, so resolved-rate comparisons across runs stay clean.
… budget exhaustion

Run-1/3 findings (50 instances, tuned template):

- Metric bug: the harness report's total_instances is the full dataset size
  (300) even with EVAL_LIMIT=50, so a 32/50 (64%) run was published as
  0.107 and nearly tripped the 0.10 threshold gate. parse_resolved now
  prefers submitted_instances over total_instances (identical for
  full-split runs).

- 6/50 instances hit LimitsExceeded after 75 steps and submitted NOTHING,
  despite forensics showing fixes can be complete mid-run. patched
  process_instance now falls back to submitting `git diff` of the working
  tree when an instance ends abnormally with a live sandbox (requires rc 0
  and a `diff --git` prefix so an error string can never become a patch).
  Empty submissions score zero, so the fallback is strictly >=.

- Stage the swebench harness report as swebench_report_<task>.json and
  upload it; it names resolved/unresolved per instance and was previously
  left behind on the node.
Run-2/3 verified the sandbox-cleanup patches (applied on the node, sweep
found 0 lingering sandboxes) but 0 fallback submissions fired while 6
instances still ended LimitsExceeded with empty patches. Root cause: mini's
agent run loop absorbs InterruptAgentFlow (Submitted, LimitsExceeded, ...)
and RETURNS normally with an empty submission -- LimitsExceeded never
reaches process_instance's except branch, which is where the fallback hook
lived (their trajectories carry no traceback/exception_str keys,
confirming the normal-return path).

Move the primary hook to just after agent.run(): any empty submission with
a live sandbox now submits `git diff` of the tree (same rc-0 +
"diff --git"-prefix guards). The except-path hook stays for real
exceptions.
Two full-300 Modal scorings measured ~$80 each in eval sandboxes alone
(vs $0.99-5.91 for image builds -- caching was never the cost driver).
Root cause: swebench's run_evaluation_modal.py hardcodes cpu=4 per
sandbox; Modal bills reserved cores and the test runs are predominantly
single-threaded pytest.

Patch the installed file at dep install (idempotent, anchor-checked,
numeric-validated) to SWEBENCH_EVAL_SANDBOX_CPU (default 2). Per-instance
tests run somewhat slower on fewer cores; scoring parallelism absorbs it.
adibarra added 3 commits July 9, 2026 20:07
Unset SWEBENCH_GEN_MODE now means the agent loop unconditionally, not
just for agentic scenarios -- SWE-bench without the agent loop is not a
meaningful eval (~10% resolved) and the 0.50 gate is calibrated to
agentic scores. single-shot remains solely as an explicit
SWEBENCH_GEN_MODE=single-shot debugging escape hatch.
@cquil11 cquil11 added the agentx AgentX benchmarks, recipes, and infrastructure label Jul 10, 2026 — with ChatGPT Codex Connector
@cquil11 cquil11 removed the agentx AgentX benchmarks, recipes, and infrastructure label Jul 10, 2026
adibarra added 11 commits July 10, 2026 11:58
…the eval surface

From a 6-dimension review (bash correctness, python/patch code, workflow
wiring, docs-vs-behavior, credential hygiene, failure modes) with
adversarial verification (41 raw -> 17 confirmed, 24 refuted):

- SWEBENCH_USE_MODAL=false no longer passes --modal (the :+ expansion
  fired on any non-empty value, including "false")
- EVAL_LIMIT is validated in the agentic path: positive integer, "full",
  or 0 -- a negative/garbage value silently short-circuited the
  completion watchdog
- fail-fast when the task YAML dataset_path is not SWE-bench_Lite:
  agentic generation is hardcoded to the lite subset, and a divergent
  scoring dataset would mis-score every instance
- set -u safety: bare ${RESULT_FILENAME} and ${SPEC_DECODING} in
  append_lm_eval_summary
- explicit UTF-8 (reads tolerant, writes clean) on all swebench_score.py
  file I/O
- sandbox-sweep scope documented precisely: confined to the current Modal
  environment; concurrent agentic-eval legs need per-leg MODAL_ENVIRONMENT
  (workflow follow-up) before the matrix ever fans out in parallel
- docs/comments made truthful: knob defaults (workers 64, step limit 75),
  gen-mode input descriptions (empty = agentic), stale 0.10-threshold and
  dev-Mac-only framing, dangling _patch_swebench_agent_cleanup reference
…ng utilization)

run_instance_modal never finalizes its ModalSandboxRuntime -- the __exit__
that terminates the sandbox exists but nothing calls it -- so every eval
sandbox idle-bills after its tests finish until the 30-min sandbox timeout
or ephemeral-app teardown. Invisible on the 50-slice (all-fast tests, the
app ends in ~2 min and reaps everything); on full-300 the slow tail keeps
the app alive ~40 min and all 300 sandboxes bill ~30 min for ~3 min of
work: measured 152 sandbox-hours ($41.57 at cpu=2) where real test time is
~15-20 sandbox-hours.

Patch (same install-time mechanism, idempotent, anchor-checked): a
finally: on run_instance_modal's main try/except chain terminates the
sandbox on every exit path. Expected full-300 scoring: ~$41 -> ~$5-8.
_patch_swebench_scoring_cpu renamed _patch_swebench_scoring (cpu +
lifecycle hunks).
…andboxes

Modal's default sandbox reservation is fractional-core, and the agents run
real test suites inside these sandboxes (verify-before-submit), where a
starved CPU can eat the 300s command timeout and waste agent steps. Optional
knob threads through mini's modal_sandbox_kwargs -> swe-rex ->
modal.Sandbox.create; unset preserves the Modal default (current behavior).
Added for the cost/time pareto sweep.
…imeout

Post-lifecycle-patch, real test runs bill ~33s each and the scoring wall
(~29 min on full-300) is set almost entirely by the 7 persistently-erroring
instances running to the harness's 1800s default. Optional pass-through to
run_evaluation --timeout; unset preserves the harness default.
… rename, app-scoped sweep

Pareto-sweep conclusions (11 runs):

- SWEBENCH_AGENT_WORKERS defaults to the config's CONC (else 64): the eval
  drives the server at the concurrency its config was tuned for. At conc144
  this cut full-run generation 90m -> 51m at identical score (five full
  runs: 160-163/300); the old w144 hang risk is contained by the completion
  watchdog (three clean w144 runs since).
- SWEBENCH_EVAL_TIMEOUT defaults to 900s: real test runs bill ~33s; only
  the persistently-erroring instances touch the ceiling and they gate the
  scoring tail.
- swe-rex's hardcoded Modal app name is patched to SWEBENCH_MODAL_APP_NAME
  (default infx-evals-swe) so the dashboard shows ours, not the library's.
- The post-generation sweep is now scoped to that app via
  Sandbox.list(app_id=...) -- it can no longer touch other apps' sandboxes
  in the shared workspace (narrows the concurrent-tenant hazard to
  same-app legs only).

Agent-sandbox CPU knob stays unset by default: the sweep measured ~zero
command timeouts at Modal's fractional-core default (1 in 300 on the full
set) -- the agent loop is inference-bound and boosting is pure cost.
The SWE-bench eval patches three pinned eval-tooling packages at install
time (mini-swe-agent, swe-rex, swebench harness) -- sandbox-lifecycle and
cost fixes measured at ~17x Modal spend reduction. No inference engine or
serving stack is touched; the waiver is filed proactively because the
mechanical shape (heredoc patches in benchmark_lib.sh) matches Check 9's
inline-patch pattern.
…gine/serving patches (vendor patchwork), not our own eval-harness tooling; vLLM runs as shipped throughout
…fresh EVALS.md

Merge-readiness sweep findings:

- BLOCKER: dsv4_fp4_mi355x_vllm.sh arrived via a main-merge (#2109) after the
  eval-gating rollout, so it was the only 1 of 23 single-node agentic recipes
  without the EVAL_ONLY/maybe_run_eval block. A live config targets it
  (configs/amd-master.yaml: dsv4-fp4-mi355x-vllm-agentic), which the generator
  marks for eval -- under EVAL_ONLY=true the recipe would fall through to the
  throughput replay and produce no score (failing the eval-scores gate).
  Appended the standard gating block (matches its sglang sibling).
- swebench install pinned to ==4.1.0: the harness CLI flags and the
  _patch_swebench_scoring anchors are verified against 4.1.0; an unpinned
  upgrade could drift either.
- EVALS.md refreshed to the shipped behavior: agentic-only default, 0.50 gate,
  50-slice/full run sizing, and the current knob set/defaults.
…guard test)

Second readiness sweep returned GO (no blockers; mi355x gate fix held).
Clearing the actionable nits:

- EVALS.md: --all-evals no longer says agentic configs are excluded (they're
  included and run swebench under evals-only/all-evals; excluded only from the
  default non-eval sweep).
- generate_sweep_configs.py: three "(single-shot)" comments corrected to
  agentic (generation is agentic-only).
- benchmark-tmpl.yml: eval-limit input description corrected (empty = 50-slice
  swebench default, not "full set").
- tests: cover the EVAL_LIMIT positive-integer rejection guard (-5/abc/3.5 ->
  fail fast) and the full/0 whole-split sentinels. 53 eval tests pass.

modal left unpinned deliberately: unlike the three source-patched packages, it
is a client to a live hosted service where an exact pin invites client/server
skew.
--log_samples \
--model_args "model=${MODEL_NAME},base_url=${openai_chat_base},api_key=${OPENAI_API_KEY},eos_string=</s>,max_retries=5,num_concurrent=${concurrent_requests},timeout=1800,tokenized_requests=False,max_length=${eval_context_len}" \
--gen_kwargs "max_tokens=${max_output_tokens},temperature=${temperature},top_p=${top_p}"
--gen_kwargs "max_tokens=${max_output_tokens},temperature=${temperature},top_p=${top_p}" \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are these rquired now? but not before

Comment thread benchmarks/benchmark_lib.sh Outdated
Comment thread benchmarks/benchmark_lib.sh Outdated
fi
if [ -f "${HOME:-}/.modal.toml" ]; then return 0; fi
if [ -n "${MODAL_TOKEN_ID:-}" ] && [ -n "${MODAL_TOKEN_SECRET:-}" ]; then
# On b300 slurm/pyxis, --export=ALL may propagate the HOST's HOME into

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kinda tech debt to have one off fix for b300 instead of just fixing root cause ? dont have full context here maybe this is the only way

The lm-eval sitecustomize, mini-swe-agent/swe-rex, and swebench Modal
scorer patches were embedded in benchmark_lib.sh as heredocs. Move each
verbatim into a standalone Python file under utils/evals/patches/ (with
the rationale comments as docstrings) and have the _patch_* shell
helpers invoke them via a BASH_SOURCE-anchored path, matching how
run_lm_eval already anchors task YAMLs.

Co-authored-by: Cameron Quilici <60715037+cquil11@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread utils/evals/thresholds.json Outdated
"default": {
"gsm8k": 0.90,
"gpqa_diamond_cot_n_shot": 0.30
"gpqa_diamond_cot_n_shot": 0.30,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@claude Not that it matters, but change this entire file to YAML and make any corresponding changes, because we use YAML everywhere else in this repository.

@Klaud-Cold Klaud-Cold Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude finished @cquil11's task in 6m 4s —— View job


Converted thresholds.jsonthresholds.yaml

  • Identify target file (utils/evals/thresholds.json, from the review thread)
  • Find all references to thresholds.json in the repo
  • Convert the file to YAML and update all readers
  • Update tests/docs
  • Run relevant tests (59 passed; 1 pre-existing, unrelated failure — see below)
  • Commit and push → 2289a2e

What changed

  • utils/evals/thresholds.jsonutils/evals/thresholds.yaml, same content (verified programmatically that the YAML parses to a dict identical to the old JSON, including glm5.1 staying a string key).
  • utils/evals/validate_scores.py: load_config now parses with yaml.safe_load, and the default path is thresholds.yaml. Docstring, example config, and --thresholds help text updated.
  • utils/evals/EVALS.md and a comment in test_swebench_eval.py updated to the new filename.

One design note: PyYAML on runner hosts

validate_scores.py runs bare (python3 utils/evals/validate_scores.py) on the self-hosted GPU runner hosts in benchmark-tmpl.yml / benchmark-multinode-tmpl.yml, with no pip install step — and nothing else running on those hosts currently imports yaml. To keep this safe:

  • If PyYAML is present (it ships on virtually every Ubuntu host via cloud-init), the YAML config loads normally.
  • If PyYAML is missing, the loader falls back to stdlib json — legacy JSON configs passed via --thresholds still work (JSON is a YAML subset either way), and the checked-in YAML config fails with an explicit install it with 'pip install pyyaml' error rather than silently weakening the score gate.

If a runner host ever hits that error, the fix is a one-line pip install pyyaml on the host (or in the workflow step).

Verification

  • test_batched_eval.py + test_swebench_eval.py + test_run_eval_dispatch.py: 59 passed, 1 failed — the failure (test_include_path_absent_when_eval_include_path_unset) also fails on the untouched tree in this environment (it's sensitive to running from a real repo checkout where run_lm_eval anchors relative task paths to absolute), so it's unrelated to this change.
  • End-to-end: ran validate_scores.py against the new YAML with a qwen3.5 per-model override — correctly fails 0.93 < 0.94 and passes 0.95 ≥ 0.94.
  • Simulated a missing-PyYAML environment: legacy JSON config loads, YAML config raises the actionable error.
    | Branch

github-actions Bot and others added 3 commits July 13, 2026 21:43
The repo uses YAML for configuration everywhere else, so move the eval
thresholds config to YAML too. validate_scores.py now parses the config
with yaml.safe_load (JSON is a YAML subset, so legacy JSON configs via
--thresholds still load); on runner hosts without PyYAML, JSON configs
fall back to the stdlib json module and YAML configs fail with an
actionable error instead of silently weakening the gate.

Requested by @cquil11 in PR #1947 review.

Co-authored-by: Cameron Quilici <60715037+cquil11@users.noreply.github.com>

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread benchmarks/benchmark_lib.sh Outdated

maybe_run_eval() {
local port="${1:-${PORT:-8888}}"
if [ "${RUN_EVAL}" = "true" ]; then

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need this gate if gated earlier with EVALS_ONLY

RUN_EVAL flag is a old workaround flag back when throughput is ran with evals as a single job

Comment thread benchmarks/benchmark_lib.sh Outdated
fi
}

maybe_run_eval() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe call this something else

# Unified eval entrypoint
# ------------------------------

run_eval() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

call this instead of the maybe_run_eval() directly?

done

local scenario_default="lm-eval"
if [ "${IS_AGENTIC:-0}" = "1" ] || [ "${SCENARIO_TYPE:-}" = "agentic-coding" ]; then

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will be preventive when adding gpqa/aime later

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

4 participants